一、引言
对于C/S架构来说,软件更新是一个很常用的功能,下面介绍一种非常实用的软件自动升级方案。
二、示意图
![](https://img2020.cnblogs.com/blog/1227623/202109/1227623-20210913000539051-701877980.png)
三、项目说明
3.1、项目创建
新建4个项目,如下所示:
![](https://img2020.cnblogs.com/blog/1227623/202109/1227623-20210913085436029-220607013.png)
3.2、项目关系
![](https://img2020.cnblogs.com/blog/1227623/202109/1227623-20210913091725592-1384450951.png)
四、LinkTo.Toolkit
LinkTo.Toolkit主要是一些Utility及Helper类文件,实现转换扩展、文件读写、进程处理等功能。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
///
/// 转换扩展类
///
public static class ConvertExtension
{
public static string ToString2(this object obj)
{
if (obj == null)
return string.Empty;
return obj.ToString();
}
public static DateTime? ToDateTime(this string str)
{
if (string.IsNullOrEmpty(str)) return null;
if (DateTime.TryParse(str, out DateTime dateTime))
{
return dateTime;
}
return null;
}
public static bool ToBoolean(this string str)
{
if (string.IsNullOrEmpty(str)) return false;
return str.ToLower() == bool.TrueString.ToLower();
}
public static bool IsNullOrEmpty(this string str)
{
return string.IsNullOrEmpty(str);
}
public static int ToInt(this string str)
{
if (int.TryParse(str, out int intValue))
{
return intValue;
}
return 0;
}
public static long ToLong(this string str)
{
if (long.TryParse(str, out long longValue))
{
return longValue;
}
return 0;
}
public static decimal ToDecimal(this string str)
{
if (decimal.TryParse(str, out decimal decimalValue))
{
return decimalValue;
}
return 0;
}
public static double ToDouble(this string str)
{
if (double.TryParse(str, out double doubleValue))
{
return doubleValue;
}
return 0;
}
public static float ToFloat(this string str)
{
if (float.TryParse(str, out float floatValue))
{
return floatValue;
}
return 0;
}
///
/// DataRow转换为实体类
///
///
///
///
public static T ConvertToEntityByDataRow(this DataRow dataRow) where T : new()
{
Type type = typeof(T);
PropertyInfo[] properties = type.GetProperties();
T t = new T();
if (dataRow == null) return t;
foreach (PropertyInfo property in properties)
{
foreach (DataColumn column in dataRow.Table.Columns)
{
if (property.Name.Equals(column.ColumnName, StringComparison.OrdinalIgnoreCase))
{
object value = dataRow[column];
if (value != null && value != DBNull.Value)
{
if (value.GetType().Name != property.PropertyType.Name)
{
if (property.PropertyType.IsEnum)
{
property.SetValue(t, Enum.Parse(property.PropertyType, value.ToString()), null);
}
else
{
try
{
value = Convert.ChangeType(value, (Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType));
property.SetValue(t, value, null);
}
catch { }
}
}
else
{
property.SetValue(t, value, null);
}
}
else
{
property.SetValue(t, null, null);
}
break;
}
}
}
return t;
}
///
/// 通用简单实体类型互转
///
public static T ConvertToEntity(this object sourceEntity) where T : new()
{
T t = new T();
Type sourceType = sourceEntity.GetType();
if (sourceType.Equals(typeof(DataRow)))
{
//DataRow类型
DataRow dataRow = sourceEntity as DataRow;
t = dataRow.ConvertToEntityByDataRow();
}
else
{
Type type = typeof(T);
PropertyInfo[] properties = type.GetProperties();
PropertyInfo[] sourceProperties = sourceType.GetProperties();
foreach (PropertyInfo property in properties)
{
foreach (var sourceProperty in sourceProperties)
{
if (sourceProperty.Name.Equals(property.Name, StringComparison.OrdinalIgnoreCase))
{
object value = sourceProperty.GetValue(sourceEntity, null);
if (value != null && value != DBNull.Value)
{
if (sourceProperty.PropertyType.Name != property.PropertyType.Name)
{
if (property.PropertyType.IsEnum)
{
property.SetValue(t, Enum.Parse(property.PropertyType, value.ToString()), null);
}
else
{
try
{
value = Convert.ChangeType(value, (Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType));
property.SetValue(t, value, null);
}
catch { }
}
}
else
{
property.SetValue(t, value, null);
}
}
else
{
property.SetValue(t, null, null);
}
break;
}
}
}
}
return t;
}
///
/// 通用简单实体类型互转
///
public static List ConvertToEntityList(this object list) where T : new()
{
List t = new List();
if (list == null) return t;
Type sourceObj = list.GetType();
if (sourceObj.Equals(typeof(DataTable)))
{
var dataTable = list as DataTable;
t = dataTable.Rows.Cast().Where(m => !(m.RowState == DataRowState.Deleted || m.RowState == DataRowState.Detached)).Select(m => m.ConvertToEntityByDataRow()).ToList();
}
else if (list is IEnumerable)
{
t = ((IList)list).Cast().Select(m => m.ConvertToEntity()).ToList();
}
return t;
}
///
/// 转换为DataTable,如果是集合没有数据行的时候会抛异常。
///
///
///
public static DataTable ConvertToDataTable(this object list)
{
if (list == null) return null;
DataTable dataTable = new DataTable();
if (list is IEnumerable)
{
var li = (IList)list;
//li[0]代表的是一个对象,list没有行时,会抛异常。
PropertyInfo[] properties = li[0].GetType().GetProperties();
dataTable.Columns.AddRange(properties.Where(m => !m.PropertyType.IsClass || !m.PropertyType.IsInterface).Select(m =>
new DataColumn(m.Name, Nullable.GetUnderlyingType(m.PropertyType) ?? m.PropertyType)).ToArray());
foreach (var item in li)
{
DataRow dataRow = dataTable.NewRow();
foreach (PropertyInfo property in properties.Where(m => m.PropertyType.GetProperty("Item") == null)) //过滤含有索引器的属性
{
object value = property.GetValue(item, null);
dataRow[property.Name] = value ?? DBNull.Value;
}
dataTable.Rows.Add(dataRow);
}
}
else
{
PropertyInfo[] properties = list.GetType().GetProperties();
properties = properties.Where(m => m.PropertyType.GetProperty("Item") == null).ToArray(); //过滤含有索引器的属性
dataTable.Columns.AddRange(properties.Select(m => new DataColumn(m.Name, Nullable.GetUnderlyingType(m.PropertyType) ?? m.PropertyType)).ToArray());
DataRow dataRow = dataTable.NewRow();
foreach (PropertyInfo property in properties)
{
object value = property.GetValue(list, null);
dataRow[property.Name] = value ?? DBNull.Value;
}
dataTable.Rows.Add(dataRow);
}
return dataTable;
}
///
/// 实体类公共属性值复制
///
///
///
public static void CopyTo(this object entity, object target)
{
if (target == null) return;
if (entity.GetType() != target.GetType())
return;
PropertyInfo[] properties = target.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.PropertyType.GetProperty("Item") != null)
continue;
object value = property.GetValue(entity, null);
if (value != null)
{
if (value is ICloneable)
{
property.SetValue(target, (value as ICloneable).Clone(), null);
}
else
{
property.SetValue(target, value.Copy(), null);
}
}
else
{
property.SetValue(target, null, null);
}
}
}
public static object Copy(this object obj)
{
if (obj == null) return null;
object targetDeepCopyObj;
Type targetType = obj.GetType();
if (targetType.IsValueType == true)
{
targetDeepCopyObj = obj;
}
else
{
targetDeepCopyObj = Activator.CreateInstance(targetType); //创建引用对象
MemberInfo[] memberCollection = obj.GetType().GetMembers();
foreach (MemberInfo member in memberCollection)
{
if (member.GetType().GetProperty("Item") != null)
continue;
if (member.MemberType == MemberTypes.Field)
{
FieldInfo field = (FieldInfo)member;
object fieldValue = field.GetValue(obj);
if (fieldValue is ICloneable)
{
field.SetValue(targetDeepCopyObj, (fieldValue as ICloneable).Clone());
}
else
{
field.SetValue(targetDeepCopyObj, fieldValue.Copy());
}
}
else if (member.MemberType == MemberTypes.Property)
{
PropertyInfo property = (PropertyInfo)member;
MethodInfo method = property.GetSetMethod(false);
if (method != null)
{
object propertyValue = property.GetValue(obj, null);
if (propertyValue is ICloneable)
{
property.SetValue(targetDeepCopyObj, (propertyValue as ICloneable).Clone(), null);
}
else
{
property.SetValue(targetDeepCopyObj, propertyValue.Copy(), null);
}
}
}
}
}
return targetDeepCopyObj;
}
}
ConvertExtension.cs
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class FileHelper
{
private readonly string strUpdateFilesPath;
public FileHelper(string strDirector)
{
strUpdateFilesPath = strDirector;
}
//保存所有的文件信息
private List listFiles = new List();
public List GetAllFilesInDirectory(string strDirector)
{
DirectoryInfo directory = new DirectoryInfo(strDirector);
DirectoryInfo[] directoryArray = directory.GetDirectories();
FileInfo[] fileInfoArray = directory.GetFiles();
if (fileInfoArray.Length > 0) listFiles.AddRange(fileInfoArray);
foreach (DirectoryInfo item in directoryArray)
{
DirectoryInfo directoryA = new DirectoryInfo(item.FullName);
DirectoryInfo[] directoryArrayA = directoryA.GetDirectories();
GetAllFilesInDirectory(item.FullName);
}
return listFiles;
}
public string[] GetUpdateList(List listFileInfo)
{
var fileArrary = listFileInfo.Cast().Select(s => s.FullName.Replace(strUpdateFilesPath, "")).ToArray();
return fileArrary;
}
///
/// 删除文件夹下的所有文件但不删除目录
///
///
public static void DeleteDirAllFile(string dirRoot)
{
DirectoryInfo directoryInfo = new DirectoryInfo(Path.GetDirectoryName(dirRoot));
FileInfo[] files = directoryInfo.GetFiles("*.*", SearchOption.AllDirectories);
foreach (FileInfo item in files)
{
File.Delete(item.FullName);
}
}
}
FileHelper.cs
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public static class FileUtility
{
#region 读取文件
///
/// 读取文件
///
/// 文件路径
///
public static string ReadFile(string filePath)
{
string result = string.Empty;
if (File.Exists(filePath) == false)
{
return result;
}
try
{
using (var streamReader = new StreamReader(filePath, Encoding.UTF8))
{
result = streamReader.ReadToEnd();
}
}
catch (Exception)
{
result = string.Empty;
}
return result;
}
#endregion 读文件
#region 写入文件
///
/// 写入文件
///
/// 文件路径
/// 写入内容
///
public static bool WriteFile(string filePath, string strValue)
{
try
{
if (File.Exists(filePath) == false)
{
using (FileStream fileStream = File.Create(filePath)) { }
}
using (var streamWriter = new StreamWriter(filePath, true, Encoding.UTF8))
{
streamWriter.WriteLine(strValue);
}
return true;
}
catch (Exception)
{
return false;
}
}
#endregion
#region 删除文件
///
/// 删除文件
///
/// 文件路径
///
public static bool DeleteFile(string filePath)
{
try
{
File.Delete(filePath);
return true;
}
catch (Exception)
{
return false;
}
}
#endregion 删除文件
#region 为文件添加用户组的完全控制权限
///
/// 为文件添加用户组的完全控制权限
///
/// 用户组
/// 文件路径
///
public static bool AddSecurityControll2File(string userGroup, string filePath)
{
try
{
//获取文件信息
FileInfo fileInfo = new FileInfo(filePath);
//获得该文件的访问权限
FileSecurity fileSecurity = fileInfo.GetAccessControl();
//添加用户组的访问权限规则--完全控制权限
fileSecurity.AddAccessRule(new FileSystemAccessRule(userGroup, FileSystemRights.FullControl, AccessControlType.Allow));
//设置访问权限
fileInfo.SetAccessControl(fileSecurity);
//返回结果
return true;
}
catch (Exception)
{
//返回结果
return false;
}
}
#endregion
#region 为文件夹添加用户组的完全控制权限
///
/// 为文件夹添加用户组的完全控制权限
///
/// 用户组
/// 文件夹路径
///
public static bool AddSecurityControll2Folder(string userGroup,string dirPath)
{
try
{
//获取文件夹信息
DirectoryInfo dir = new DirectoryInfo(dirPath);
//获得该文件夹的所有访问权限
DirectorySecurity dirSecurity = dir.GetAccessControl(AccessControlSections.All);
//设定文件ACL继承
InheritanceFlags inherits = InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit;
//添加用户组的访问权限规则--完全控制权限
FileSystemAccessRule usersFileSystemAccessRule = new FileSystemAccessRule(userGroup, FileSystemRights.FullControl, inherits, PropagationFlags.None, AccessControlType.Allow);
dirSecurity.ModifyAccessRule(AccessControlModification.Add, usersFileSystemAccessRule, out bool isModified);
//设置访问权限
dir.SetAccessControl(dirSecurity);
//返回结果
return true;
}
catch (Exception)
{
//返回结果
return false;
}
}
#endregion
}
FileUtility.cs
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public static class ProcessUtility
{
#region 关闭进程
///
/// 关闭进程
///
/// 进程名
public static void KillProcess(string processName)
{
Process[] myproc = Process.GetProcesses();
foreach (Process item in myproc)
{
if (item.ProcessName == processName)
{
item.Kill();
}
}
}
#endregion
}
ProcessUtility.cs
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
///
/// Xml序列化与反序列化
///
public static class XmlUtility
{
#region 序列化
///
/// 序列化
///
/// 类型
/// 对象
///
public static string Serializer(Type type, object obj)
{
MemoryStream Stream = new MemoryStream();
XmlSerializer xml = new XmlSerializer(type);
try
{
//序列化对象
xml.Serialize(Stream, obj);
}
catch (InvalidOperationException)
{
throw;
}
Stream.Position = 0;
StreamReader sr = new StreamReader(Stream);
string str = sr.ReadToEnd();
sr.Dispose();
Stream.Dispose();
return str;
}
#endregion 序列化
#region 反序列化
///
/// 反序列化
///
/// 类型
/// XML字符串
///
public static object Deserialize(Type type, string xml)
{
try
{
using (StringReader sr = new StringReader(xml))
{
XmlSerializer xmldes = new XmlSerializer(type);
return xmldes.Deserialize(sr);
}
}
catch (Exception ex)
{
return ex.Message;
}
}
///
/// 反序列化
///
///
///
///
public static object Deserialize(Type type, Stream stream)
{
XmlSerializer xmldes = new XmlSerializer(type);
return xmldes.Deserialize(stream);
}
#endregion 反序列化
}
XmlUtility.cs
五、AutoUpdaterTest
5.1、实体类
作用:本地配置AutoUpdateConfig.xml文件的序列化及反序列化实体对象。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class AutoUpdateConfig
{
///
/// 自动升级模式:当前仅支持HTTP
///
public string AutoUpdateMode { get; set; }
///
/// HTTP自动升级模式时的URL地址
///
public string AutoUpdateHttpUrl { get; set; }
}
AutoUpdateConfig.cs
5.2、通用类
作用:应用程序全局静态常量。全局参数都在此设置,方便统一管理。注:客户端是否检测更新,也是在此设置默认值。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
///
/// 应用程序全局静态常量
///
public static class GlobalParam
{
#region 自动更新参数
///
/// 是否检查自动更新:默认是true
///
public static string CheckAutoUpdate = "true";
///
/// 本地自动更新配置XML文件名
///
public const string AutoUpdateConfig_XmlFileName = "AutoUpdateConfig.xml";
///
/// 本地自动更新下载临时存放目录
///
public const string TempDir = "Temp";
///
/// 远端自动更新信息XML文件名
///
public const string AutoUpdateInfo_XmlFileName = "AutoUpdateInfo.xml";
///
/// 远端自动更新文件存放目录
///
public const string RemoteDir = "AutoUpdateFiles";
///
/// 主线程名
///
public const string MainProcess = "AutoUpdaterTest";
#endregion
}
GlobalParam.cs
作用:应用程序上下文。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
///
/// 应用程序上下文
///
public class AppContext
{
///
/// 客户端配置文件
///
public static AutoUpdateConfig AutoUpdateConfigData { get; set; }
}
AppContext.cs
作用:应用程序配置。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class AppConfig
{
private static readonly object _lock = new object();
private static AppConfig _instance = null;
#region 自动更新配置
///
/// 自动更新配置数据
///
public AutoUpdateConfig AutoUpdateConfigData { get; set; }
private AppConfig()
{
AutoUpdateConfigData = new AutoUpdateConfig();
}
public static AppConfig Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new AppConfig();
}
}
}
return _instance;
}
}
///
/// 本地自动更新下载临时文件夹路径
///
public string TempPath
{
get
{
return string.Format("{0}\\{1}", Application.StartupPath, GlobalParam.TempDir);
}
}
///
/// 初始化系统配置信息
///
public void InitialSystemConfig()
{
AutoUpdateConfigData.AutoUpdateMode = AppContext.AutoUpdateConfigData.AutoUpdateMode;
AutoUpdateConfigData.AutoUpdateHttpUrl = AppContext.AutoUpdateConfigData.AutoUpdateHttpUrl;
}
#endregion
}
AppConfig.cs
5.3、工具类
作用:配置文件的读写。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class AutoUpdateHelper
{
private readonly string AutoUpdateMode = string.Empty;
public AutoUpdateHelper(string autoUpdateMode)
{
AutoUpdateMode = autoUpdateMode;
}
///
/// 加载本地自动更新配置文件
///
///
public static AutoUpdateConfig Load()
{
string filePath = string.Empty, fileContent = string.Empty;
filePath = Path.Combine(Application.StartupPath, GlobalParam.AutoUpdateConfig_XmlFileName);
AutoUpdateConfig config = new AutoUpdateConfig();
fileContent = FileUtility.ReadFile(filePath);
object obj = XmlUtility.Deserialize(typeof(AutoUpdateConfig), fileContent);
config = obj as AutoUpdateConfig;
return config;
}
///
/// 获取远端自动更新信息的版本号
///
///
public string GetRemoteAutoUpdateInfoVersion()
{
XDocument doc = new XDocument();
doc = XDocument.Parse(GetRemoteAutoUpdateInfoXml());
return doc.Element("AutoUpdateInfo").Element("NewVersion").Value;
}
///
/// 获取远端自动更新信息的XML文件内容
///
///
public string GetRemoteAutoUpdateInfoXml()
{
string remoteXmlAddress = AppConfig.Instance.AutoUpdateConfigData.AutoUpdateHttpUrl + "/" + GlobalParam.AutoUpdateInfo_XmlFileName;
string receiveXmlPath = Path.Combine(AppConfig.Instance.TempPath, GlobalParam.AutoUpdateInfo_XmlFileName);
string xmlString = string.Empty;
if (Directory.Exists(AppConfig.Instance.TempPath) == false)
{
Directory.CreateDirectory(AppConfig.Instance.TempPath);
}
if (AutoUpdateMode.ToUpper() == "HTTP")
{
WebClient client = new WebClient();
client.DownloadFile(remoteXmlAddress, receiveXmlPath);
}
if (File.Exists(receiveXmlPath))
{
xmlString = FileUtility.ReadFile(receiveXmlPath);
return xmlString;
}
return string.Empty;
}
///
/// 写入本地自动更新配置的XML文件内容
///
///
public string WriteLocalAutoUpdateInfoXml()
{
string xmlPath = string.Empty, xmlValue = string.Empty;
xmlPath = Path.Combine(AppConfig.Instance.TempPath, GlobalParam.AutoUpdateConfig_XmlFileName);
xmlValue = XmlUtility.Serializer(typeof(AutoUpdateConfig), AppConfig.Instance.AutoUpdateConfigData);
if (File.Exists(xmlPath))
{
File.Delete(xmlPath);
}
bool blSuccess = FileUtility.WriteFile(xmlPath, xmlValue);
return blSuccess == true ? xmlPath : "";
}
}
AutoUpdateHelper.cs
5.4、本地配置文件
作用:配置自动更新模式及相关。
注1:复制到输出目录选择始终复制。
注2:主程序运行时,先读取此配置更新文件,然后给AppContext上下文赋值,接着给AppConfig配置赋值。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
HTTP
http://127.0.0.1:6600/AutoUpdateDir
AutoUpdateConfig.xml
5.5、主程序
新建一个Windows 窗体MainForm,此处理仅需要一个空白窗体即可,作测试用。
![](https://img2020.cnblogs.com/blog/1227623/202109/1227623-20210913115407389-730712587.png)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public partial class MainForm : Form
{
private static MainForm _Instance;
///
/// MainForm主窗体实例
///
public static MainForm Instance
{
get
{
if (_Instance == null)
{
_Instance = new MainForm();
}
return _Instance;
}
}
public MainForm()
{
InitializeComponent();
}
}
MainForm.cs
5.6、应用程序主入口
作用:检测应用程序是否需要自动更新,如里需要则检测远程服务端的版本号。假如远程服务端有新版本,则调用自动更新器AutoUpdater并向其传递4个参数。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
internal static class Program
{
///
/// 应用程序的主入口点
///
[STAThread]
private static void Main(string[] args)
{
//尝试设置访问权限
FileUtility.AddSecurityControll2Folder("Users", Application.StartupPath);
//未捕获的异常处理
Application.ThreadException += Application_ThreadException;
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
//是否检查自动更新赋值
if (args.Length > 0)
{
GlobalParam.CheckAutoUpdate = args[0];
}
//加载自动更新配置文件,给上下文AppServiceConfig对象赋值。
var config = AutoUpdateHelper.Load();
AppContext.AutoUpdateConfigData = config;
//窗体互斥体
var instance = new Mutex(true, GlobalParam.MainProcess, out bool isNewInstance);
if (isNewInstance == true)
{
if (GlobalParam.CheckAutoUpdate.ToBoolean())
{
if (CheckUpdater())
ProcessUtility.KillProcess(GlobalParam.MainProcess);
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(MainForm.Instance);
instance.ReleaseMutex();
}
else
{
MessageBox.Show("已经启动了一个程序,请先退出。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
Application.Exit();
}
}
///
/// 自动更新检测
///
///
private static bool CheckUpdater()
{
if (GlobalParam.CheckAutoUpdate.ToBoolean() == false) return false;
#region 检查版本更新
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
bool blFinish = false;
AppConfig.Instance.InitialSystemConfig();
var helper = new AutoUpdateHelper(AppConfig.Instance.AutoUpdateConfigData.AutoUpdateMode);
string fileVersion = FileVersionInfo.GetVersionInfo(Application.ExecutablePath).FileVersion;
long localVersion = 0;
long remoteVersion = 0;
try
{
localVersion = fileVersion.Replace(".", "").ToLong();
remoteVersion = helper.GetRemoteAutoUpdateInfoVersion().Replace(".", "").ToLong();
if ((localVersion > 0) && (localVersion
{
try
{
var downFileList = _autoUpdateInfo.FileList.OrderByDescending(s => s.IndexOf("\\"));
foreach (var fileName in downFileList)
{
string fileUrl = string.Empty, fileVaildPath = string.Empty;
if (fileName.StartsWith("\\"))
{
fileVaildPath = fileName.Substring(fileName.IndexOf("\\"));
}
else
{
fileVaildPath = fileName;
}
fileUrl = _autoUpdateHttpUrl.TrimEnd(new char[] { '/' }) + @"/" + GlobalParam.RemoteDir + @"/" + fileVaildPath.Replace("\\", "/"); //替换文件目录中的路径为网络路径
DownloadFileDetail(fileUrl, fileName);
}
_blSuccess = true;
}
catch (Exception ex)
{
BeginInvoke(new MethodInvoker(() =>
{
throw ex;
}));
}
finally
{
BeginInvoke(new MethodInvoker(delegate ()
{
btnRun.Enabled = true;
btnLeave.Enabled = true;
}));
}
if (_blSuccess)
{
Process.Start(GlobalParam.MainProcess + ".exe");
BeginInvoke(new MethodInvoker(delegate ()
{
Close();
Application.Exit();
}));
}
})
{
IsBackground = true
};
thread.Start();
}
private void DownloadFileDetail(string httpUrl, string filename)
{
string fileName = Application.StartupPath + "\\" + filename;
string dirPath = GetDirPath(fileName);
if (!Directory.Exists(dirPath))
{
Directory.CreateDirectory(dirPath);
}
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(httpUrl);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream httpStream = response.GetResponseStream();
long totalBytes = response.ContentLength;
if (progressBar != null)
{
BeginInvoke(new MethodInvoker(delegate ()
{
lblDownInfo.Text = "开始下载...";
progressBar.Maximum = (int)totalBytes;
progressBar.Minimum = 0;
}));
}
FileStream outputStream = new FileStream(fileName, FileMode.Create);
int bufferSize = 2048;
int readCount;
byte[] buffer = new byte[bufferSize];
readCount = httpStream.Read(buffer, 0, bufferSize);
int allByte = (int)response.ContentLength;
int startByte = 0;
BeginInvoke(new MethodInvoker(delegate ()
{
progressBar.Maximum = allByte;
progressBar.Minimum = 0;
}));
while (readCount > 0)
{
outputStream.Write(buffer, 0, readCount);
readCount = httpStream.Read(buffer, 0, bufferSize);
startByte += readCount;
BeginInvoke(new MethodInvoker(delegate ()
{
lblDownInfo.Text = "已下载:" + startByte / 1024 + "KB/" + "总长度:"+ allByte / 1024 + "KB" + " " + " 文件名:" + filename;
progressBar.Value = startByte;
}));
Application.DoEvents();
Thread.Sleep(5);
}
BeginInvoke(new MethodInvoker(delegate ()
{
lblDownInfo.Text = "下载完成。";
}));
httpStream.Close();
outputStream.Close();
response.Close();
}
public static string GetDirPath(string filePath)
{
if (filePath.LastIndexOf("\\") > 0)
{
return filePath.Substring(0, filePath.LastIndexOf("\\"));
}
return filePath;
}
///
/// 暂不更新
///
///
///
private void btnLeave_Click(object sender, EventArgs e)
{
if (MessageBox.Show("确定要放弃此次更新吗?", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
Process.Start(GlobalParam.MainProcess + ".exe", "false");
Close();
Application.Exit();
}
}
}
HttpStartUp.cs
6.4、应用程序主入口
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
internal static class Program
{
///
/// 应用程序的主入口点
///
[STAThread]
private static void Main(string[] args)
{
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
#region 测试
//string strArgs = @"E:\LinkTo.AutoUpdater\AutoUpdaterTest\bin\Debug\Temp\AutoUpdateConfig.xml"+" "+@"E:\LinkTo.AutoUpdater\AutoUpdaterTest\bin\Debug\Temp\AutoUpdateInfo.xml"+" "+"AutoUpdaterTest"+" "+"AutoUpdateFiles";
//args = strArgs.Split(' ');
#endregion
if (args.Length > 0)
{
string autoUpdateConfigXmlPath = args[0].ToString();
string autoUpdateInfoXmlPath = args[1].ToString();
GlobalParam.MainProcess = args[2].ToString();
GlobalParam.RemoteDir = args[3].ToString();
var autoUpdateConfigXml = FileUtility.ReadFile(autoUpdateConfigXmlPath);
var autoUpdateInfoXml = FileUtility.ReadFile(autoUpdateInfoXmlPath);
AutoUpdateConfig config = XmlUtility.Deserialize(typeof(AutoUpdateConfig), autoUpdateConfigXml) as AutoUpdateConfig;
AutoUpdateInfo info = XmlUtility.Deserialize(typeof(AutoUpdateInfo), autoUpdateInfoXml) as AutoUpdateInfo;
if (config.AutoUpdateMode.ToUpper() == "HTTP")
{
Application.Run(new HttpStartUp(config.AutoUpdateHttpUrl, info));
}
}
else
{
Application.Exit();
}
}
///
/// UI线程未捕获异常处理
///
///
///
public static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
{
string strError = "", strLog = "", strDateInfo = DateTime.Now.ToString() + " 出现应用程序未处理的异常:\r\n";
var error = e.Exception;
if (error != null)
{
strError = strDateInfo + $"异常类型:{error.GetType().Name}\r\n异常消息:{error.Message}";
strLog = strDateInfo + $"异常类型:{error.GetType().Name}\r\n异常消息:{error.Message}\r\n堆栈信息:{error.StackTrace}\r\n来源信息:{error.Source}\r\n";
}
else
{
strError = $"Application ThreadException:{e}";
}
WriteLog(strLog);
MessageBox.Show(strError, "系统错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
///
/// 非UI线程未捕获异常处理
///
///
///
public static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
string strError = "", strLog = "", strDateInfo = DateTime.Now.ToString() + " 出现应用程序未处理的异常:\r\n";
if (e.ExceptionObject is Exception error)
{
strError = strDateInfo + $"异常消息:{error.Message}";
strLog = strDateInfo + $"异常消息:{error.Message}\r\n堆栈信息:{error.StackTrace}";
}
else
{
strError = $"Application UnhandledError:{e}";
}
WriteLog(strLog);
MessageBox.Show(strError, "系统错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
///
/// 写入日志
///
///
private static void WriteLog(string strLog)
{
string dirPath = @"Log\AutoUpdater", fileName = DateTime.Now.ToString("yyyy-MM-dd") + ".txt";
string strLine = "----------------------------------------------------------------------------------------------------";
FileUtility.WriteFile(Path.Combine(dirPath, fileName), strLog);
FileUtility.WriteFile(Path.Combine(dirPath, fileName), strLine);
}
}
Program.cs
七、AutoUpdateXmlBuilder
7.1、实体类
作用:自动更新内容信息。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
///
/// 自动更新内容信息
///
[Serializable]
public class AutoUpdateInfo
{
///
/// 新版本号
///
public string NewVersion { get; set; }
///
/// 更新日期
///
public string UpdateTime { get; set; }
///
/// 更新内容说明
///
public string UpdateContent { get; set; }
///
/// 更新文件列表
///
public List FileList { get; set; }
}
AutoUpdateInfo.cs
7.2、通用类
作用:应用程序全局静态常量。全局参数都在此设置,方便统一管理。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
///
/// 应用程序全局静态常量
///
public static class GlobalParam
{
///
/// 远端自动更新信息XML文件名
///
public const string AutoUpdateInfo_XmlFileName = "AutoUpdateInfo.xml";
///
/// 远端自动更新目录
///
public const string AutoUpdateDir = "AutoUpdateDir";
///
/// 远端自动更新文件存放目录
///
public const string RemoteDir = "AutoUpdateFiles";
///
/// 主线程名
///
public const string MainProcess = "AutoUpdaterTest";
}
GlobalParam.cs
7.3、Window 窗体
1)新建一个Windows 窗体Main。
![](https://img2020.cnblogs.com/blog/1227623/202109/1227623-20210913123307173-1237086209.png)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public partial class Main : Form
{
//自动更新目录路径
private static readonly string AutoUpdateDirPath = Application.StartupPath + @"\" + GlobalParam.AutoUpdateDir;
//自动更新信息XML文件路径
private static readonly string AutoUpdateInfoXmlPath = Path.Combine(AutoUpdateDirPath, GlobalParam.AutoUpdateInfo_XmlFileName);
//自动更新文件目录路径
private static readonly string RemoteDirPath = Application.StartupPath + @"\" + GlobalParam.AutoUpdateDir + @"\" + GlobalParam.RemoteDir;
public Main()
{
InitializeComponent();
}
///
/// 窗体加载
///
///
///
private void Main_Load(object sender, EventArgs e)
{
if (!Directory.Exists(RemoteDirPath))
{
Directory.CreateDirectory(RemoteDirPath);
}
LoadBaseInfo();
LoadDirectoryFileList();
}
///
/// 刷新
///
///
///
private void btnRefresh_Click(object sender, EventArgs e)
{
LoadBaseInfo();
LoadDirectoryFileList();
}
///
/// 初始化
///
private void LoadBaseInfo()
{
dtUpdateTime.Text = DateTime.Now.ToString("yyyy-MM-dd");
txtNewVersion.Text = GetMainProcessFileVersion();
CreateHeaderAndFillListView();
}
///
/// 获取主程序文件版本
///
///
private string GetMainProcessFileVersion()
{
string fileVersion = "";
if (File.Exists(RemoteDirPath + "\\" + GlobalParam.MainProcess + ".exe")) //如果更新中有主程序文件
{
FileVersionInfo info = FileVersionInfo.GetVersionInfo(RemoteDirPath + "\\" + GlobalParam.MainProcess + ".exe");
fileVersion = info.FileVersion;
}
return fileVersion;
}
///
/// 添加ListView列名
///
private void CreateHeaderAndFillListView()
{
lstFileList.Columns.Clear();
int lvWithd = lstFileList.Width;
ColumnHeader columnHeader;
//First Header
columnHeader = new ColumnHeader
{
Text = "#",
Width = 38
};
lstFileList.Columns.Add(columnHeader);
//Second Header
columnHeader = new ColumnHeader
{
Text = "文件名",
Width = (lvWithd - 38) / 2
};
lstFileList.Columns.Add(columnHeader);
//Third Header
columnHeader = new ColumnHeader
{
Text = "更新路径",
Width = (lvWithd - 38) / 2
};
lstFileList.Columns.Add(columnHeader);
}
///
/// 加载目录文件列表
///
private void LoadDirectoryFileList()
{
if (!Directory.Exists(RemoteDirPath))
{
Directory.CreateDirectory(RemoteDirPath);
}
FileHelper fileHelper = new FileHelper(RemoteDirPath);
var fileArrary = fileHelper.GetUpdateList(fileHelper.GetAllFilesInDirectory(RemoteDirPath)).ToList();
var lastFile = fileArrary.FirstOrDefault(s => s == GlobalParam.MainProcess + ".exe");
//exe作为最后的文件更新,防止更新过程中出现网络错误,导致文件未全部更新。
if (lastFile != null)
{
fileArrary.Remove(lastFile);
fileArrary.Add(lastFile);
}
PopulateListViewWithArray(fileArrary.ToArray());
}
///
/// 使用路径字符数组填充列表
///
///
private void PopulateListViewWithArray(string[] strArray)
{
lstFileList.Items.Clear();
if (strArray != null)
{
//只过滤根目录下的特殊文件
strArray = strArray.Where(s => !new string[] { GlobalParam.AutoUpdateInfo_XmlFileName }.Contains(s.Substring(s.IndexOf('\\') + 1))).ToArray();
for (int i = 0; i < strArray.Length; i++)
{
ListViewItem lvi = new ListViewItem
{
Text = (i + 1).ToString()
};
int intStart = strArray[i].LastIndexOf('\\') + 1;
lvi.SubItems.Add(strArray[i].Substring(intStart, strArray[i].Length - intStart));
lvi.SubItems.Add(strArray[i]);
lstFileList.Items.Add(lvi);
}
}
}
///
/// 生成更新XML文件
///
///
///
private void btnBuild_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtNewVersion.Text))
{
MessageBox.Show("更新版本号不能为空。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
txtNewVersion.Focus();
return;
}
if (string.IsNullOrEmpty(txtMainProcessName.Text))
{
MessageBox.Show("主进程名不能为空。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
txtMainProcessName.Focus();
return;
}
AutoUpdateInfo info = new AutoUpdateInfo()
{
NewVersion = txtNewVersion.Text.Trim(),
UpdateTime = dtUpdateTime.Value.ToString("yyyy-MM-dd"),
UpdateContent = txtUpdateContent.Text,
FileList = lstFileList.Items.Cast().Select(s => s.SubItems[2].Text).ToList()
};
string xmlValue = XmlUtility.Serializer(typeof(AutoUpdateInfo), info);
using (StreamWriter sw = new StreamWriter(AutoUpdateInfoXmlPath))
{
sw.WriteLine(xmlValue);
sw.Flush();
sw.Close();
}
MessageBox.Show("生成成功。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
///
/// 打开本地目录
///
///
///
private void btnOpen_Click(object sender, EventArgs e)
{
ProcessStartInfo psi = new ProcessStartInfo("Explorer.exe")
{
Arguments = AutoUpdateDirPath
};
Process.Start(psi);
}
}
Main.cs
2)在bin\Debug\下新建一个AutoUpdateDir文件夹,然后再在AutoUpdateDir下新建一个AutoUpdateFiles文件夹。
3)在AutoUpdaterTest中,将程序集版本及文件版本都改成1.0.0.1并重新生成,接着将AutoUpdaterTest.exe拷贝到AutoUpdateFiles下,最后将程序集版本及文件版本都改回1.0.0.0。
4)此时运行AutoUpdateXmlBuilder,点击生成更新XML文件即打包成功。程序会自动在AutoUpdateDir下生成打包信息文件AutoUpdateInfo.xml。
![](https://img2020.cnblogs.com/blog/1227623/202109/1227623-20210913125341626-1497591592.png)
7.4、应用程序主入口
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
internal static class Program
{
///
/// 应用程序的主入口点。
///
[STAThread]
private static void Main()
{
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Main());
}
///
/// UI线程未捕获异常处理
///
///
///
public static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
{
string strError = "", strLog = "", strDateInfo = DateTime.Now.ToString() + " 出现应用程序未处理的异常:\r\n";
var error = e.Exception;
if (error != null)
{
strError = strDateInfo + $"异常类型:{error.GetType().Name}\r\n异常消息:{error.Message}";
strLog = strDateInfo + $"异常类型:{error.GetType().Name}\r\n异常消息:{error.Message}\r\n堆栈信息:{error.StackTrace}\r\n来源信息:{error.Source}\r\n";
}
else
{
strError = $"Application ThreadException:{e}";
}
WriteLog(strLog);
MessageBox.Show(strError, "系统错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
///
/// 非UI线程未捕获异常处理
///
///
///
public static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
string strError = "", strLog = "", strDateInfo = DateTime.Now.ToString() + " 出现应用程序未处理的异常:\r\n";
if (e.ExceptionObject is Exception error)
{
strError = strDateInfo + $"异常消息:{error.Message}";
strLog = strDateInfo + $"异常消息:{error.Message}\r\n堆栈信息:{error.StackTrace}";
}
else
{
strError = $"Application UnhandledError:{e}";
}
WriteLog(strLog);
MessageBox.Show(strError, "系统错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
///
/// 写入日志
///
///
private static void WriteLog(string strLog)
{
string dirPath = @"Log\XmlBuilder", fileName = DateTime.Now.ToString("yyyy-MM-dd") + ".txt";
string strLine = "----------------------------------------------------------------------------------------------------";
FileUtility.WriteFile(Path.Combine(dirPath, fileName), strLog);
FileUtility.WriteFile(Path.Combine(dirPath, fileName), strLine);
}
}
Program.cs
八、远程服务端配置
注:此处为本机测试。
1)在某个盘符如E盘下新建一个AutoUpdate文件夹,将AutoUpdateXmlBuilder打包文件夹AutoUpdateDir拷贝到AutoUpdate文件夹下。
2)在IIS中新建一个网站,对应的虚拟目录为E:\AutoUpdate,同时将端口设置为6600。
3)运行AutoUpdaterTest,如出现自动更新器即代表成功。
![](https://img2020.cnblogs.com/blog/1227623/202109/1227623-20210913130734973-1805241823.png)
源码下载
|